﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Threading;
using System.Windows.Forms;
using System.Drawing;
using System.Configuration;

using SlimDX;
using SlimDX.DXGI;
using SlimDX.Direct3D11;

using LibSnesDotNet.Core;
using LibSnesDotNet.Threading;

using Device = SlimDX.Direct3D11.Device;
using Buffer = SlimDX.Direct3D11.Buffer;
using Configuration = System.Configuration.Configuration;

namespace LibSnesDotNet.Video
{
	class Direct3D11Driver : Direct3D11Base, IVideoDriver
	{
		WorkerThread       _workerThread;
		WaitableBool       _isBusy;

		DeviceContext      _context;
		RenderTargetView   _backBufferView;

		ShaderBytecode     _effectBytecode;
		Effect             _effect;
		EffectTechnique    _technique;
		EffectPass         _pass;
		InputLayout        _layout;
		Buffer             _vertices;
		Texture2D          _texture;
		ShaderResourceView _textureView;

		public Direct3D11Driver(Form form, Configuration config)
			: base(form, new SwapChainDescription()
			             {
				             IsWindowed = true,
				             BufferCount = 1,
				             SwapEffect = SwapEffect.Discard,
				             Usage = Usage.RenderTargetOutput,
				             Flags = SwapChainFlags.None,
				             SampleDescription = new SampleDescription(1, 0),
				             ModeDescription = new ModeDescription(form.ClientSize.Width, form.ClientSize.Height, new Rational(), Format.R8G8B8A8_UNorm)
			             })
		{
			IsDisposed = false;

			try
			{
				_workerThread = new WorkerThread();
				_isBusy = new WaitableBool(false);

				_context = D3D11Device.ImmediateContext;
				_backBufferView = GetBackBufferView();
				_context.OutputMerger.SetTargets(_backBufferView);

				Viewport viewPort = new Viewport()
				{
					X = 0,
					Y = 0,
					Width = form.ClientSize.Width,
					Height = form.ClientSize.Height,
					MinZ = 0.0F,
					MaxZ = 1.0F
				};
				_context.Rasterizer.SetViewports(viewPort);

				_effectBytecode = ShaderBytecode.CompileFromFile("Effect.fx", " ", "fx_5_0", ShaderFlags.None, EffectFlags.None, null, null);
				_effect = new Effect(D3D11Device, _effectBytecode, EffectFlags.None);

				_technique = _effect.GetTechniqueByIndex(0);
				_pass = _technique.GetPassByIndex(0);

				InputElement[] elements = new InputElement[]
				{
					new InputElement("POSITION", 0, Format.R32G32B32A32_Float,  0, 0),
					new InputElement("TEXCOORD", 0, Format.R32G32_Float,       16, 0)
				};
				_layout = new InputLayout(D3D11Device, _pass.Description.Signature, elements);

				DataStream stream = new DataStream(4 * Marshal.SizeOf(typeof(Vertex)), true, true);
				stream.Write(new Vector4(-1.0F, 1.0F, 0.5F, 1.0F));
				stream.Write(new Vector2(0.0F, 0.0F));
				stream.Write(new Vector4(1.0F, 1.0F, 0.5F, 1.0F));
				stream.Write(new Vector2(1.0F, 0.0F));
				stream.Write(new Vector4(-1.0F, -1.0F, 0.5F, 1.0F));
				stream.Write(new Vector2(0.0F, 1.0F));
				stream.Write(new Vector4(1.0F, -1.0F, 0.5F, 1.0F));
				stream.Write(new Vector2(1.0F, 1.0F));

				stream.Position = 0;

				_vertices = new Buffer(D3D11Device, stream, new BufferDescription()
				{
					SizeInBytes = (int)stream.Length,
					BindFlags = BindFlags.VertexBuffer,
					Usage = ResourceUsage.Default,
					CpuAccessFlags = CpuAccessFlags.None,
					OptionFlags = ResourceOptionFlags.None
				});
				stream.Close();

				_texture = new Texture2D(D3D11Device, new Texture2DDescription()
				{
					Width = 512,
					Height = 512,
					Format = Format.R8G8B8A8_UNorm,
					ArraySize = 1,
					MipLevels = 1,
					Usage = ResourceUsage.Dynamic,
					CpuAccessFlags = CpuAccessFlags.Write,
					BindFlags = BindFlags.ShaderResource,
					SampleDescription = SwapChainDescription.SampleDescription
				});

				_textureView = new ShaderResourceView(D3D11Device, _texture);

				_effect.GetVariableByName("InputTexture").AsResource().SetResource(_textureView);
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public IWaitableBool IsBusy { get { return new ReadOnlyWaitableBool(_isBusy); } }
		bool IVideoDriver.IsFullscreen
		{
			get { return IsFullscreen; }
			set
			{
				_isBusy.Acquire(true);
				ChangeMode(value);
				_isBusy.Value = false;
			}
		}

		public void Render(VideoUpdatedEventArgs e)
		{
			while (!TryRender(e))
			{
				_isBusy.WaitWhile();
			}
		}

		public bool TryRender(VideoUpdatedEventArgs e)
		{
			AssertUndisposed();

			if (!_isBusy.TryAcquire(true)) { return false; }

			DataBox dataBox = _context.MapSubresource(_texture, 0, 512 * 512 * 4, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None);
			unsafe
			{
				ushort* snesBuffer = (ushort*)e.VideoBuffer.DataPointer;
				byte* textureBuffer = (byte*)dataBox.Data.DataPointer;
				for (int j = 0; j < e.Height; j++)
				{
					for (int i = 0; i < e.Width; i++)
					{
						ushort color = *snesBuffer++;
						int b;

						b = ((color >> 10) & 31) * 8;
						*textureBuffer++ = (byte)(b + b / 35);
						b = ((color >> 5) & 31) * 8;
						*textureBuffer++ = (byte)(b + b / 35);
						b = ((color >> 0) & 31) * 8;
						*textureBuffer++ = (byte)(b + b / 35);
						*textureBuffer++ = 255;
					}

					textureBuffer += dataBox.RowPitch - e.Width * 4;
					snesBuffer += e.Height > 256 ? 512 - e.Width : 1024 - e.Width;
				}
			}
			dataBox.Data.Close();
			_context.UnmapSubresource(_texture, 0);

			_workerThread.DoWork(new Action(() =>
			{
				AssertUndisposed();

				_context.ClearRenderTargetView(_backBufferView, new Color4(0.0F, 0.0F, 0.0F));

				_context.InputAssembler.InputLayout = _layout;
				_context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleStrip;
				_context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(_vertices, Marshal.SizeOf(typeof(Vertex)), 0));

				_effect.GetVariableByName("TexelRange").AsVector().Set(new Vector2(e.Width / 512.0F, e.Height / 512.0F));
				_pass.Apply(_context);
				_context.Draw(4, 0);

				Present(IsComposited && !IsFullscreen ? 0 : 1, PresentFlags.None);

				_isBusy.Value = false;
			}));

			return true;
		}

		protected override void OnFullscreenToggled()
		{
			_isBusy.Acquire(true);

			base.OnFullscreenToggled();

			_isBusy.Value = false;
		}

		protected override void OnBuffersChanging()
		{
			_isBusy.Acquire(true);

			_backBufferView.Resource.Dispose();
			_backBufferView.Dispose();

			base.OnBuffersChanging();
		}

		protected override void OnBuffersChanged()
		{
			_backBufferView = GetBackBufferView();
			_context.OutputMerger.SetTargets(_backBufferView);

			Rectangle viewRect = new Rectangle();
			if (Form.ClientSize.Height * (4.0F / 3.0F) + 2 < Form.ClientSize.Width)
			{
				viewRect.Width = (int)(Form.ClientSize.Height * (4.0F / 3.0F));
				viewRect.Height = Form.ClientSize.Height;
				viewRect.X = (Form.ClientSize.Width - viewRect.Width) / 2;
				viewRect.Y = 0;
			}
			else if (Form.ClientSize.Width * (3.0F / 4.0F) + 2 < Form.ClientSize.Height)
			{
				viewRect.Width = Form.ClientSize.Width;
				viewRect.Height = (int)(Form.ClientSize.Width * (3.0F / 4.0F));
				viewRect.X = 0;
				viewRect.Y = (Form.ClientSize.Height - viewRect.Height) / 2;
			}
			else
			{
				viewRect.Width = Form.ClientSize.Width;
				viewRect.Height = Form.ClientSize.Height;
				viewRect.X = 0;
				viewRect.Y = 0;
			}

			_context.Rasterizer.SetViewports(new Viewport()
			{
				X = viewRect.X,
				Y = viewRect.Y,
				Width = viewRect.Width,
				Height = viewRect.Height,
				MinZ = 0.0F,
				MaxZ = 1.0F
			});

			base.OnBuffersChanged();

			_isBusy.Value = false;
		}

		public new bool IsDisposed { get; private set; }

		public new void Dispose()
		{
			AssertUndisposed();
			IsDisposed = true;

			if (_workerThread != null)
			{
				_workerThread.WaitFor();
				_workerThread.Dispose();
			}
			if (_context != null) { _context.Dispose(); }
			if (_backBufferView != null)
			{
				_backBufferView.Resource.Dispose();
				_backBufferView.Dispose();
			}
			if (_effectBytecode != null) { _effectBytecode.Dispose(); }
			if (_effect != null) { _effect.Dispose(); }
			if (_layout != null) { _layout.Dispose(); }
			if (_vertices != null) { _vertices.Dispose(); }
			if (_texture != null) { _texture.Dispose(); }
			if (_textureView != null) { _textureView.Dispose(); }

			base.Dispose();
		}

		public new void AssertUndisposed()
		{
			if (IsDisposed) { throw new ObjectDisposedException(ToString()); }
		}
	}

	[StructLayout(LayoutKind.Sequential, Pack = 1)]
	struct Vertex
	{
		public Vector4 Position;
		public Vector2 TexCoord;
	}
}
